<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { io, Socket } from "socket.io-client";

const called = ref<boolean>(false); // 是否接收邀请
const caller = ref<boolean>(false); // 是否发起方
const calling = ref<boolean>(false); // 是否呼叫中
const communicating = ref<boolean>(false); // 视频通话中
const localVideo = ref<HTMLVideoElement>(); // video标签实例,本人视频框
const remoteVideo = ref<HTMLVideoElement>(); // video标签实例，用户视频框
let socket = ref<Socket>();
let peer = ref<any>();
let localStream = ref<any>();
// 处理 Candidate，接收远程端设置描述的信息
const pendingCandidates: any[] = [];
const roomId = "001";

onMounted(() => {
  const sock = io("http://localhost:3000"); //后端地址
  /*****************通用方法************************ */
  //服务器用户连接成功
  sock.on("connectionSuccess", () => {
    console.log("连接成功");
    sock.emit("joinRoom", roomId);
  });

  // 服务器告诉浏览器 A发送视频请求
  sock.on("callRemote", async () => {
    if (!caller.value) {
      called.value = true;
      calling.value = true;
    }
  });
  // 处理 Candidate 1.用户A。B都会发送数据到sendCandidate
  sock.on("sendCandidate", async (data) => {
    try {
      console.log("收到 candidate:", data);

      const { candidate } = data;

      if (!peer.value) {
        pendingCandidates.push(candidate);
        return;
      }
      //需要等待远程端设置描述加载完成，才能够添加candidate，否则报错
      if (!peer.value.remoteDescription) {
        pendingCandidates.push(candidate);
        return;
      }

      await peer.value.addIceCandidate(new RTCIceCandidate(candidate));

      // 处理暂存的 candidate
      while (pendingCandidates.length > 0) {
        const cand = pendingCandidates.shift();
        await peer.value.addIceCandidate(new RTCIceCandidate(cand));
      }
    } catch (e) {
      console.error("添加候选地址失败", e);
    }
  });

  /**用户断开连接 */
  sock.on("userDisconnected", async ({ roomId, userId }) => {
    console.log("用户已断开连接:", userId);
    if (communicating.value && remoteVideo.value?.srcObject) {
      remoteVideo.value.srcObject = null;
      if (peer.value) {
        peer.value.close();
        peer.value = null;
      }
      communicating.value = false;
    }

    calling.value = false;
  });

  /**用户主动挂断 */
  sock.on("userHangUp", async ({ roomId, userId }) => {
    console.log("用户已断开连接:", userId);
    if (communicating.value && remoteVideo.value?.srcObject) {
      remoteVideo.value.srcObject = null;
      remoteVideo.value = undefined;
      if (peer.value) {
        peer.value.close();
        peer.value = null;
      }
      communicating.value = false;
    }

    calling.value = false;
  });

  /**用户重新链接,多人链接 后续扩展 */

  /**
   * ***********************A到B的视频逻辑****************************
   * A到B的视频逻辑
   * 1.A创建offer
   * 2.A发送offer给B
   * 3.peer监听candidate和answer
   * 4.A接收到B的answer
   */
  // 用户A 接收到用户B同意视频的请求
  sock.on("acceptCall", async () => {
    if (caller.value) {
      peer.value = new RTCPeerConnection();

      // 先绑定 ontrack
      peer.value.ontrack = (event: RTCTrackEvent) => {
        console.log("ontrack 收到远程轨道:", event.track);
        if (remoteVideo.value) {
          const stream = event.streams[0];
          remoteVideo.value.srcObject = stream;

          // 等待元数据加载完成再播放，由于谷歌浏览器禁止播放未加载的元数据
          remoteVideo.value.onloadedmetadata = () => {
            remoteVideo.value!.muted = true;
            remoteVideo.value!.play().catch((err) => {
              console.error("播放失败，请检查是否需要用户交互", err);
            });
          };
        }
        communicating.value = true;
        calling.value = false;
      };
      // RTC监听candidate信息
      peer.value.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
        console.log("用户A的candidate", event.candidate);
        if (event.candidate) {
          socket.value!.emit("sendCandidate", {
            roomId,
            candidate: event.candidate,
          });
        }
      };
      //获取本地媒体流
      const stream = await getLocalStream();
      //把本地流添加到peer中
      await peer.value.addStream(stream);
      //创建一个offer描述
      const offer = await peer.value.createOffer({
        offerToReceiveAudio: 1,
        offerToReceiveVideo: 1,
      });
      //简历点对点链接
      await peer.value.setLocalDescription(offer);
      // 向服务器发送offer
      socket.value?.emit("sendOffer", { roomId, offer });
    }
  });

  // 用户A 收到 Answer
  sock.on("sendAnswer", async (answer) => {
    if (caller.value) {
      const sessionDescription = new RTCSessionDescription(answer);
      //指定了连接的远程端的属性，包括媒体格式
      await peer.value.setRemoteDescription(sessionDescription);
    }
  });
  /**
   * ***********************B到A的视频逻辑****************************
   * 1. 用户B 接收到 A的Offer
   * 2. peer监听candidate和offer
   * 3. 用户B 创建 Answer
   * 4. 用户B 发送 Answer
   */
  // 用户B 收到 A的Offer
  sock.on("sendOffer", async (offer) => {
    if (called.value && !peer.value) {
      // 创建一个peer
      peer.value = new RTCPeerConnection();

      // 绑定 ontrack
      peer.value.ontrack = (event: RTCTrackEvent) => {
        console.log("用户B 收到远程轨道:", event.track);
        if (remoteVideo.value) {
          const stream = event.streams[0];
          remoteVideo.value.srcObject = stream;

          // 等待元数据加载完成再播放，由于谷歌浏览器禁止播放未加载的元数据
          remoteVideo.value.onloadedmetadata = () => {
            remoteVideo.value!.muted = true;
            remoteVideo.value!.play().catch((err) => {
              console.error("播放失败，请检查是否需要用户交互", err);
            });
          };
        }
        communicating.value = true;
        calling.value = false;
      };

      // 监听 candidate 并转发
      peer.value.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
        if (event.candidate) {
          socket.value?.emit("sendCandidate", {
            roomId,
            candidate: event.candidate,
          });
        }
      };

      // 获取本地媒体流
      const stream = await getLocalStream();
      // 监听媒体流
      await peer.value.addStream(stream);

      const sessionDescription = new RTCSessionDescription(offer);
      // 指定了连接的远程端的属性，包括媒体格式
      await peer.value.setRemoteDescription(sessionDescription);

      //  创建 answer
      const answer = await peer.value.createAnswer();
      // 点对点链接
      await peer.value.setLocalDescription(answer);

      // 发送 answer
      socket.value?.emit("sendAnswer", { roomId, answer });
    }
  });

  socket.value = sock;
});

// 获取音视频流
const getLocalStream = async () => {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: {
      noiseSuppression: true, // 启用浏览器内置降噪
      autoGainControl: true, // 自动增益控制
      echoCancellation: true, // 回声消除
    },
  });
  localVideo.value!.srcObject = stream;
  localVideo.value!.play();
  localStream.value = stream;
  return stream;
};

/***************事件************* */
// 发起视频
const callRemote = async () => {
  console.log("发起视频");
  await getLocalStream();
  caller.value = true;
  calling.value = true;
  socket.value?.emit("callRemote", roomId);
};
// 同意视频
const acceptCall = async () => {
  console.log("同意视频");
  socket.value?.emit("acceptCall", roomId);
};

const hangUp = async () => {
  console.log("挂断视频");
  // 1.停止本地流
  localStream.value
    .getTracks()
    .forEach((track: { stop: () => any }) => track.stop());

  // 2.关闭连接
  if (peer.value) {
    peer.value.close();
    peer.value = null;
  }

  // 3.发送挂断信令
  socket.value?.emit("hangUp", { roomId: roomId });

  // 4.更新界面状态
  calling.value = false;
  /**页面更新 */
  if (localVideo.value) {
    localVideo.value.srcObject = null;
    localVideo.value = undefined;
  }
  if (remoteVideo.value) {
    remoteVideo.value.srcObject = null;
    remoteVideo.value = undefined;
  }
};
</script>

<template>
  <div class="web-rtc-demo">
    <div class="web-rtc-demo-container">
      <video ref="localVideo" class="local-video"></video>
      <video ref="remoteVideo" muted class="remote-video"></video>

      <div v-if="caller && calling" class="calling">
        <p class="">等待对方接听...</p>
      </div>

      <div v-if="called && calling" class="called">
        <p class="">收到视频邀请...</p>
        <div class="bottons">
          <button @click="acceptCall" class="botton">接受</button>
          <button @click="hangUp" class="botton">拒绝</button>
        </div>
      </div>
    </div>
  </div>
  <div class="footer">
    <button @click="callRemote" class="promary">发起视频</button>
    <button @click="hangUp" class="cancel">挂断视频</button>
  </div>
</template>

<style scoped>
.web-rtc-demo {
  display: flex;
  width: 100%;
  height: 100%;
}
.web-rtc-demo-container {
  position: relative;
}
.local-video {
  /* width: 400px; */
  height: 400px;
  border: 1px solid red;
}

.remote-video {
  position: absolute;
  height: 120px;
  border: 2px solid rgb(11, 241, 11);
  bottom: 23px;
  right: 20px;
}

.calling,
.called {
  display: flex;
  position: absolute;
  color: #fff;
}
.called {
  display: flex;
  width: 100%;
  top: 0;
  flex-direction: column;
  transform: translate(0, 200px);
  justify-content: center;
  align-items: center;
  p {
    font-size: 16px;
    color: red;
  }
}
.bottons {
  display: flex;
  height: 60px;
  align-items: center;
  justify-content: center;
}

.botton {
  width: 62px;
  height: 30px;
  font-size: 14px;
  color: #fff;
}
.botton:first-child {
  background: red;
}
.botton:last-child {
  margin-left: 10px;
  background: blue;
}
.footer {
  width: 100%;
  height: 30px;
  display: flex;
}
.promary {
  background-color: #4caf50;
  color: white;
  width: 100px;
  height: 30px;
  border: 1px solid #4caf50;
}
.cancel {
  background-color: red;
  color: white;
  width: 100px;
  height: 30px;
  border: 1px solid red;
  margin-left: 10px;
}
</style>
